﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net;
using System.Web;

using WebImageResizer.Filters;
using WebImageResizer.Utils;

namespace WebImageResizer
{
	/// <summary>
	/// Http Handler
	/// </summary>
	public class ImageHandler : IHttpHandler
	{
		#region Readonly & Static Fields

		private readonly List<IImageFilter> m_ImageFilters = new List<IImageFilter>(
			new IImageFilter[]
				{
					new ResizeFilter(), // Default Filters
					new ZoomFilter(),
					new RotateFilter(),
					new GreyScaleFilter(),
				});

		/// <summary>
		/// Cache Manager Reference
		/// </summary>
		private static readonly IImageStore s_ImageStore = Factory.GetImageStore();
		private static readonly IImageTool s_ImageTool = Factory.GetImageTool();

		#endregion

		#region Instance Properties

		public IImageFilter[] ImageFilters
		{
			get { return m_ImageFilters.ToArray(); }
		}

		#endregion

		#region Instance Methods

		public void AddFilter(IImageFilter imageFilter)
		{
			m_ImageFilters.Add(imageFilter);
		}

		public void RemoveFilter(IImageFilter imageFilter)
		{
			m_ImageFilters.Remove(imageFilter);
		}

		/// <summary>
		/// Takes care of resizeing image from url parameters
		/// </summary>
		/// <param name="context"></param>
		/// <param name="imageFormat"></param>
		/// <returns></returns>
		private byte[] GetImageData(HttpContext context, ImageFormat imageFormat)
		{
			string src = context.Request["src"];
			IImageProvider provider = Factory.GetImageProvider();
			if (provider.IsNull())
			{
				throw new ApplicationException("Unable to determine image provider");
			}

			byte[] data = provider.GetImageData(src);
			if (data.IsNull())
			{
				return null;
			}

			using (MemoryStream sourceImageData = new MemoryStream(data))
			{
				Bitmap outputImg = (Bitmap) Image.FromStream(sourceImageData);
				foreach (IImageFilter imageFilter in m_ImageFilters)
				{
					Bitmap oldOutputImage = outputImg;
					bool modified = imageFilter.Process(context, ref outputImg);
					if (modified)
					{
						// Dispose old bitmap
						oldOutputImage.Dispose();
					}
				}

				using (outputImg)
				{
					// Encode image
					return s_ImageTool.Encode(outputImg, imageFormat);
				}
			}
		}

		#endregion

		#region IHttpHandler Members

		/// <summary>
		/// Retrieve or update cache, and write to output stream
		/// </summary>
		/// <param name="context"></param>
		public void ProcessRequest(HttpContext context)
		{
			string key = context.Request.Url.ToString().ToLowerInvariant();
			string eTag = "\"" + key.GetHashCode() + "\"";
			if (CheckETag(context, eTag))
			{
				return;
			}

			ImageFormat imageFormat = GetImageFormat(context);

			byte[] imageData = s_ImageStore.GetImageData(key);
			if (imageData.IsNull())
			{
				imageData = GetImageData(context, imageFormat);
				if (imageData.IsNull())
				{
					return;
				}

				s_ImageStore.PutImageData(key, imageData);
			}

			context.Response.Cache.SetCacheability(HttpCacheability.Public);
			context.Response.Cache.SetETag(eTag);
			context.Response.Cache.SetExpires(DateTime.Now.AddYears(1));
			context.Response.ContentType = "image/" + imageFormat.ToString().ToLower();
			context.Response.OutputStream.Write(imageData, 0, imageData.Length);
		}

		public bool IsReusable
		{
			get { return false; }
		}

		#endregion

		#region Class Methods

		/// <summary>
		/// Check if the ETag that sent from the client is match to the current ETag.
		/// If so, set the status code to 'Not Modified' and stop the response.
		/// </summary>
		private static bool CheckETag(HttpContext context, string eTagCode)
		{
			string ifNoneMatch = context.Request.Headers["If-None-Match"];
			if (eTagCode.Equals(ifNoneMatch, StringComparison.Ordinal))
			{
				context.Response.AppendHeader("Content-Length", "0");
				context.Response.StatusCode = (int) HttpStatusCode.NotModified;
				context.Response.StatusDescription = "Not modified";
				context.Response.SuppressContent = true;
				context.Response.Cache.SetCacheability(HttpCacheability.Public);
				context.Response.Cache.SetETag(eTagCode);
				context.Response.Cache.SetExpires(DateTime.Now.AddYears(1));
				context.Response.End();
				return true;
			}

			return false;
		}

		private static ImageFormat GetImageFormat(HttpContext context)
		{
			string imageTypeParameter = context.Request["format"];
			if (!string.IsNullOrEmpty(imageTypeParameter))
			{
				switch (imageTypeParameter.ToLowerInvariant())
				{
					case "png":
						return ImageFormat.Png;
					case "gif":
						return ImageFormat.Gif;
					case "jpg":
						return ImageFormat.Jpeg;
					case "tif":
						return ImageFormat.Tiff;
				}
			}

			return ImageFormat.Jpeg;
		}

		#endregion
	}
}